using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using DataAccess.BusinessEntities;
using DataAccess;
using Locator.LcboServices.LcboProductsService.Services;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Logging;
using Microsoft.Practices.ObjectBuilder;
using Locator.LcboServices.BusinessEntities;
using Microsoft.Practices.CompositeWeb;
using Locator.LcboServices.LcboStoresService.Services;
using DataAccess.Services;
using SignalR;

namespace Locator.Admin
{
    public class AdminController : IAdminController
    {
        private readonly TraceManager traceManager;
        private readonly LogWriter logWriter;
        private readonly IUnitOfWork unitOfWork;
        private const string containerIdFormat = "{0}x{1:0.00}";
        private readonly ILcboProductsService lcboProductsService;
        private readonly ILcboStoresService lcboStoresService;
        private readonly IFastDomainContext domainContext;


        public AdminController
            (
            [CreateNew] IUnitOfWork unitOfWork,
            [ServiceDependency] ILcboProductsService lcboProductsService,
            [ServiceDependency] ILcboStoresService lcboStoresService,
            [CreateNew] IFastDomainContext domainContext
            )
        {
            this.unitOfWork = unitOfWork;
            this.logWriter = EnterpriseLibraryContainer.Current.GetInstance<LogWriter>();
            this.traceManager = new TraceManager(this.logWriter);
            this.lcboProductsService = lcboProductsService;
            this.lcboStoresService = lcboStoresService;
            this.domainContext = domainContext;
        }

        public string ScanProducts(IConnection signalrConnection)
        {
            StringBuilder result = new StringBuilder();
            //lcboProductsService = new LcboProductServiceAgent(new LivePageRetrieverFactory());
            Stopwatch getProductsStopwatch = new Stopwatch();
            getProductsStopwatch.Start();
            //unitOfWork = new SqlUnitOfWork();
            var lcboProducts = lcboProductsService.GetAllProducts();

            var substanceDict = BuildSubstanceDictionaryAndUpdateDbIfNeeded2(lcboProducts, result, signalrConnection);
            var containerDict = BuildContainerDictionaryAndUpdateDbIfNeeded2(lcboProducts, result, signalrConnection);
            UpdateProductsInDb2(lcboProducts, substanceDict, containerDict, result, signalrConnection);
            getProductsStopwatch.Stop();
            signalrConnection.Broadcast(String.Format("Elapsed time in seconds: {0:0.00}", getProductsStopwatch.ElapsedMilliseconds / 1000.00));
            result.AppendFormat("Elapsed time in seconds: {0:0.00}\r\n", getProductsStopwatch.ElapsedMilliseconds / 1000.00);
            return result.ToString();
        }

        private Dictionary<string, Substance> BuildSubstanceDictionaryAndUpdateDbIfNeeded(List<LcboProduct> lcboProducts, StringBuilder result)
        {
            Dictionary<string, Substance> knownSubstancesDictionary = new Dictionary<string, Substance>();
            IEnumerable<string> allLcboSubstanceNames = from p in lcboProducts select p.Name;
            IQueryable<string> knownSubstanceNames = from s in unitOfWork.Substances.FindAll() select s.Name;
            List<string> knownSubstanceNamesList = new List<string>();

            // Force the query to evaluate and put the results in a list
            foreach (string substanceName in knownSubstanceNames)
            {
                knownSubstanceNamesList.Add(substanceName);
            }

            result.AppendFormat("There are {0} substances in the DB\r\n", knownSubstanceNames.Count());

            IEnumerable<string> newSubstanceNames = from s in allLcboSubstanceNames.Distinct()
                                                    where !knownSubstanceNamesList.Contains(s)
                                                    select s;

            int newSubstanceCount = 0;
            foreach (var substance in newSubstanceNames)
            {
                Substance newSubstance = new Substance() { Name = substance };
                unitOfWork.Substances.Add(newSubstance);
                newSubstanceCount++;
            }

            result.AppendFormat("There are {0} new substances on the LCBO website\r\n", newSubstanceCount);

            unitOfWork.Commit();
            result.Append("Updated DB\r\n");


            foreach (Substance substance in unitOfWork.Substances.FindAll())
            {
                knownSubstancesDictionary.Add(substance.Name, substance);
            }

            result.AppendFormat("Built dictionary of {0} substances\r\n", knownSubstancesDictionary.Count);
            return knownSubstancesDictionary;
        }

        private Dictionary<string, Substance> BuildSubstanceDictionaryAndUpdateDbIfNeeded2(List<LcboProduct> lcboProducts, StringBuilder result, IConnection signalrConnection)
        {
            Dictionary<string, Substance> knownSubstancesDictionary = new Dictionary<string, Substance>();
            IEnumerable<string> allLcboSubstanceNames = from p in lcboProducts select p.Name;
            IQueryable<string> knownSubstanceNames = from s in domainContext.Substances select s.Name;
            List<string> knownSubstanceNamesList = new List<string>();

            // Force the query to evaluate and put the results in a list
            foreach (string substanceName in knownSubstanceNames)
            {
                knownSubstanceNamesList.Add(substanceName);
            }

            signalrConnection.Broadcast(String.Format("There are {0} substances in the DB", knownSubstanceNames.Count()));
            result.AppendFormat("There are {0} substances in the DB\r\n", knownSubstanceNames.Count());

            IEnumerable<string> newSubstanceNames = from s in allLcboSubstanceNames.Distinct()
                                                    where !knownSubstanceNamesList.Contains(s)
                                                    select s;

            int newSubstanceCount = 0;
            foreach (var substance in newSubstanceNames)
            {
                Substance newSubstance = new Substance() { Name = substance };
                domainContext.Save(newSubstance);
                newSubstanceCount++;
            }

            signalrConnection.Broadcast(String.Format("There are {0} new substances on the LCBO website\r\n", newSubstanceCount));
            result.AppendFormat("There are {0} new substances on the LCBO website\r\n", newSubstanceCount);

            domainContext.SaveChanges();
            signalrConnection.Broadcast("Updated DB");
            result.Append("Updated DB\r\n");


            foreach (Substance substance in domainContext.Substances)
            {
                knownSubstancesDictionary.Add(substance.Name, substance);
            }

            signalrConnection.Broadcast(String.Format("Built dictionary of {0} substances", knownSubstancesDictionary.Count));
            result.AppendFormat("Built dictionary of {0} substances\r\n", knownSubstancesDictionary.Count);
            return knownSubstancesDictionary;
        }

        private void UpdateProductsInDb(List<LcboProduct> lcboProducts, Dictionary<string, Substance> substanceDict, Dictionary<string, Container> containerDict, StringBuilder result)
        {
            //Dictionary<int, Product> knownProductsDictionary = new Dictionary<int, Product>();
            List<int> knownProductIdsList = new List<int>();

            foreach (var product in unitOfWork.Products.FindAll())
            {
                knownProductIdsList.Add(product.Id);
            }

            result.AppendFormat("There are {0} products in the DB\r\n", knownProductIdsList.Count());

            Dictionary<int, LcboProduct> allLcboProducts = new Dictionary<int, LcboProduct>();
            foreach (var lcboProduct in lcboProducts)
            {
                allLcboProducts.Add(lcboProduct.Id, lcboProduct);
            }

            IEnumerable<LcboProduct> newProducts = from c in allLcboProducts.Values
                                                   where !knownProductIdsList.Contains(c.Id)
                                                   select c;

            int newProductCount = 0;
            foreach (var lcboProduct in newProducts)
            {
                Product product = new Product()
                {
                    Id = lcboProduct.Id,
                    Substance = substanceDict[lcboProduct.Name],
                    Container = containerDict[String.Format(containerIdFormat, lcboProduct.Container.NumberOfContainers, lcboProduct.Container.ContainerCapacity)]
                };

                unitOfWork.Products.Add(product);
                newProductCount++;
            }

            unitOfWork.Commit();
            result.AppendFormat("There are {0} new products on the LCBO website\r\n", newProducts.Count());
        }

        private void UpdateProductsInDb2(List<LcboProduct> lcboProducts, Dictionary<string, Substance> substanceDict, Dictionary<string, Container> containerDict, StringBuilder result, IConnection signalrConnection)
        {
            //Dictionary<int, Product> knownProductsDictionary = new Dictionary<int, Product>();
            List<int> knownProductIdsList = new List<int>();

            foreach (var product in domainContext.Products)
            {
                knownProductIdsList.Add(product.Id);
            }

            signalrConnection.Broadcast(String.Format("There are {0} products in the DB", knownProductIdsList.Count()));
            result.AppendFormat("There are {0} products in the DB\r\n", knownProductIdsList.Count());

            Dictionary<int, LcboProduct> allLcboProducts = new Dictionary<int, LcboProduct>();
            foreach (var lcboProduct in lcboProducts)
            {
                allLcboProducts.Add(lcboProduct.Id, lcboProduct);
            }

            IEnumerable<LcboProduct> newProducts = from c in allLcboProducts.Values
                                                   where !knownProductIdsList.Contains(c.Id)
                                                   select c;

            int newProductCount = 0;
            foreach (var lcboProduct in newProducts)
            {
                Product product = new Product()
                {
                    Id = lcboProduct.Id,
                    Substance = substanceDict[lcboProduct.Name],
                    Container = containerDict[String.Format(containerIdFormat, lcboProduct.Container.NumberOfContainers, lcboProduct.Container.ContainerCapacity)]
                };

                domainContext.Save(product);
                newProductCount++;
            }

            domainContext.SaveChanges();
            signalrConnection.Broadcast(String.Format("There are {0} new products on the LCBO website", newProducts.Count()));
            result.AppendFormat("There are {0} new products on the LCBO website\r\n", newProducts.Count());
        }

        private Dictionary<string, Container> BuildContainerDictionaryAndUpdateDbIfNeeded(List<LcboProduct> lcboProducts, StringBuilder result)
        {
            Dictionary<string, Container> knownContainerDictionary = new Dictionary<string, Container>();
            List<string> knownContainerIdsList = new List<string>();

            // Force the query to evaluate and put the results in a list
            foreach (var container in unitOfWork.Containers.FindAll())
            {
                string containerKey = String.Format(containerIdFormat, container.NumberOfContainers, container.ContainerCapacity);
                knownContainerIdsList.Add(containerKey);
            }

            Dictionary<string, LcboContainerFormat> uniqueLcboContainers = new Dictionary<string, LcboContainerFormat>();
            foreach (LcboProduct lcboProduct in lcboProducts)
            {
                string lcboContainerKey = String.Format(containerIdFormat, lcboProduct.Container.NumberOfContainers, lcboProduct.Container.ContainerCapacity);
                if (!uniqueLcboContainers.ContainsKey(lcboContainerKey))
                {
                    uniqueLcboContainers.Add(lcboContainerKey, lcboProduct.Container);
                }
            }

            result.AppendFormat("There are {0} containers in the DB\r\n", knownContainerIdsList.Count());

            IEnumerable<LcboContainerFormat> newContainers = from c in uniqueLcboContainers.Values
                                                             where !knownContainerIdsList.Contains(String.Format(containerIdFormat, c.NumberOfContainers, c.ContainerCapacity))
                                                             select c;

            int newContainerCount = 0;
            foreach (var lcboContainer in newContainers)
            {
                Container newContainer = new Container()
                {
                    ContainerCapacity = lcboContainer.ContainerCapacity,
                    NumberOfContainers = lcboContainer.NumberOfContainers
                };

                unitOfWork.Containers.Add(newContainer);
                newContainerCount++;
            }

            result.AppendFormat("There are {0} new containers on the LCBO website\r\n", newContainerCount);

            unitOfWork.Commit();
            result.Append("Updated DB\r\n");

            foreach (Container container in unitOfWork.Containers.FindAll())
            {
                knownContainerDictionary.Add(String.Format(containerIdFormat, container.NumberOfContainers, container.ContainerCapacity), container);
            }

            result.AppendFormat("Built dictionary of {0} containers\r\n", knownContainerDictionary.Count);
            return knownContainerDictionary;
        }

        private Dictionary<string, Container> BuildContainerDictionaryAndUpdateDbIfNeeded2(List<LcboProduct> lcboProducts, StringBuilder result, IConnection signalrConnection)
        {
            Dictionary<string, Container> knownContainerDictionary = new Dictionary<string, Container>();
            List<string> knownContainerIdsList = new List<string>();

            // Force the query to evaluate and put the results in a list
            foreach (var container in domainContext.Containers)
            {
                string containerKey = String.Format(containerIdFormat, container.NumberOfContainers, container.ContainerCapacity);
                knownContainerIdsList.Add(containerKey);
            }

            Dictionary<string, LcboContainerFormat> uniqueLcboContainers = new Dictionary<string, LcboContainerFormat>();
            foreach (LcboProduct lcboProduct in lcboProducts)
            {
                string lcboContainerKey = String.Format(containerIdFormat, lcboProduct.Container.NumberOfContainers, lcboProduct.Container.ContainerCapacity);
                if (!uniqueLcboContainers.ContainsKey(lcboContainerKey))
                {
                    uniqueLcboContainers.Add(lcboContainerKey, lcboProduct.Container);
                }
            }

            signalrConnection.Broadcast(String.Format("There are {0} containers in the DB", knownContainerIdsList.Count()));
            result.AppendFormat("There are {0} containers in the DB\r\n", knownContainerIdsList.Count());

            IEnumerable<LcboContainerFormat> newContainers = from c in uniqueLcboContainers.Values
                                                             where !knownContainerIdsList.Contains(String.Format(containerIdFormat, c.NumberOfContainers, c.ContainerCapacity))
                                                             select c;

            int newContainerCount = 0;
            foreach (var lcboContainer in newContainers)
            {
                Container newContainer = new Container()
                {
                    ContainerCapacity = lcboContainer.ContainerCapacity,
                    NumberOfContainers = lcboContainer.NumberOfContainers
                };

                domainContext.Save(newContainer);
                newContainerCount++;
            }

            signalrConnection.Broadcast(String.Format("There are {0} new containers on the LCBO website", newContainerCount));
            result.AppendFormat("There are {0} new containers on the LCBO website\r\n", newContainerCount);

            domainContext.SaveChanges();
            signalrConnection.Broadcast("Updated DB");
            result.Append("Updated DB\r\n");

            foreach (Container container in domainContext.Containers)
            {
                knownContainerDictionary.Add(String.Format(containerIdFormat, container.NumberOfContainers, container.ContainerCapacity), container);
            }

            signalrConnection.Broadcast(String.Format("Built dictionary of {0} containers", knownContainerDictionary.Count));
            result.AppendFormat("Built dictionary of {0} containers\r\n", knownContainerDictionary.Count);
            return knownContainerDictionary;
        }

        public string ScanStores(IConnection signalrConnection)
        {
            StringBuilder result = new StringBuilder();

            Stopwatch getProductsStopwatch = new Stopwatch();
            getProductsStopwatch.Start();

            var storesInDb = unitOfWork.Stores.FindAll();
            Dictionary<int, Store> dbStoreDictionary = new Dictionary<int, Store>();
            int storesInDbCount = 0;
            foreach (Store store in storesInDb)
            {
                storesInDbCount++;
                dbStoreDictionary.Add(store.Id, store);
            }

            signalrConnection.Broadcast(String.Format("Found {0} stores in the DB", storesInDbCount));
            result.AppendFormat("Found {0} stores in the DB\r\n", storesInDbCount);

            var lcboStores = lcboStoresService.GetAllStores();

            signalrConnection.Broadcast(String.Format("Found {0} stores on the LCBO website", lcboStores.Count));
            result.AppendFormat("Found {0} stores on the LCBO website\r\n", lcboStores.Count);

            Dictionary<int, LcboStoreInformation> lcboStoresDictionary = new Dictionary<int, LcboStoreInformation>();

            List<int> lcboStoreIds = new List<int>();
            foreach (LcboStoreInformation lcboStore in lcboStores)
            {
                lcboStoresDictionary.Add(lcboStore.StoreNumber, lcboStore);
                lcboStoreIds.Add(lcboStore.StoreNumber);
            }

            lcboStoreIds.Sort();
            //Console.WriteLine("LCBO ctore numbers are:");
            //foreach (int lcboStoreId in lcboStoreIds)
            //{
            //    Console.WriteLine(lcboStoreId);
            //}

            // Figure out new and changed stores and update them in the unitOfWork
            int newStoreCount = 0;
            foreach (int lcboStoreId in lcboStoresDictionary.Keys)
            {

                LcboStoreInformation lcboStore = lcboStoresDictionary[lcboStoreId];
                if (dbStoreDictionary.ContainsKey(lcboStoreId))
                {
                    Store dbStore = dbStoreDictionary[lcboStoreId];
                    dbStore.Address = lcboStore.AddressLine;
                    dbStore.City = lcboStore.City;
                    dbStore.Latitude = lcboStore.Latitude;
                    dbStore.Longitude = lcboStore.Longitude;
                }
                else
                {
                    Store newDbStore = new Store()
                    {
                        Address = lcboStore.AddressLine,
                        City = lcboStore.City,
                        Id = lcboStore.StoreNumber,
                        Latitude = lcboStore.Latitude,
                        Longitude = lcboStore.Longitude,
                        IsDeleted = false
                    };

                    newStoreCount++;
                    unitOfWork.Stores.Add(newDbStore);
                }
            }

            // Identify closed stores i.e. in the DB but not on the LCBO site
            int deletedStores = 0;
            foreach (int dbStoreId in dbStoreDictionary.Keys)
            {
                if (!lcboStoresDictionary.ContainsKey(dbStoreId))
                {
                    Store storeToDelete = dbStoreDictionary[dbStoreId];
                    unitOfWork.Stores.Remove(storeToDelete);
                    deletedStores++;
                }
            }

            signalrConnection.Broadcast(String.Format("Deleted {0} stores", deletedStores));
            unitOfWork.Commit();

            getProductsStopwatch.Stop();
            signalrConnection.Broadcast(String.Format("Found {0} new stores on the LCBO website", newStoreCount));
            result.AppendFormat("Found {0} new stores on the LCBO website\r\n", newStoreCount);
            signalrConnection.Broadcast(String.Format("Elapsed time in seconds: {0:0.00}", getProductsStopwatch.ElapsedMilliseconds / 1000.00));
            result.AppendFormat("Elapsed time in seconds: {0:0.00}\r\n", getProductsStopwatch.ElapsedMilliseconds / 1000.00);
            return result.ToString();
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.unitOfWork != null)
                {
                    this.unitOfWork.Dispose();
                }

                if (this.logWriter != null)
                {
                    this.logWriter.Dispose();
                }

                if (this.domainContext != null)
                {
                    this.domainContext.Dispose();
                }

                if (this.lcboProductsService != null)
                {
                    this.lcboProductsService.Dispose();
                }

                if (this.lcboStoresService != null)
                {
                    this.lcboStoresService.Dispose();
                }
            }
        }

        ~AdminController()
        {
            this.Dispose(false);
        }
    }
}
